<template>
  <div :class="['container', { 'alternate-theme': isAlternateTheme }]">
    <div :class="['sidebar', { 'alternate-theme': isAlternateTheme }]">
      <div class="search-bar">
        <t-input v-model="searchQuery" placeholder="搜索消息..." clearable @clear="clearSearch" @change="handleSearch">
        </t-input>
      </div>
      <t-button variant="outline" @click="toggleTheme">切换主题
        <template #icon>
          <ViewListIcon />
        </template>
      </t-button>
      <t-button variant="outline" @click="openDownloadModal" class="sidebar-button">
        下载对话
        <template #icon>
          <DownloadIcon />
        </template>
      </t-button>
      <t-button variant="outline" @click="openShareModal" class="sidebar-button">
        <template #icon>
          <Share1Icon />
        </template>
        分享对话
      </t-button>
    </div>
    <div class="chat-container">
      <t-chat ref="chatRef" layout="single" style="height: 800px" :clear-history="chatList.length > 0 && !isStreamLoad"
        @clear="clearConfirm">
        <template v-for="(item, index) in chatList" :key="index">
          <t-chat-item :avatar="item.avatar" :name="item.name" :role="item.role" :datetime="item.datetime"
            :text-loading="index === 0 && loading">
            <template #content>
              <div :class="['chat-bubble', item.role === 'user' ? 'user-bubble' : 'assistant-bubble']"
                v-html="parseLatex(item.content)"></div>
            </template>
            <template #actions>
              <div class="custom-action-container">
                <t-chat-action :is-good="isGood[index]" :is-bad="isBad[index]" :content="item.content"
                  @operation="(type, { e }) => handleOperation(type, { e, index })" class="custom-action-buttons">
                </t-chat-action>
                <t-button variant="outline" @click="confirmDelete(index)" class="delete-icon-button" title="删除"
                  aria-label="删除消息">
                  <template #icon>
                    <DeleteIcon />
                  </template>
                </t-button>
                <t-button variant="outline" @click="quoteMessage(index)" class="quote-icon-button" title="引用"
                  aria-label="引用消息">
                  <template #icon>
                    <ChatIcon />
                  </template>
                </t-button>
                <t-button variant="outline" @click="openShareModal()" class="share-icon-button" title="分享"
                  aria-label="分享回答">
                  <template #icon>
                    <Share1Icon />
                  </template>
                </t-button>
              </div>
            </template>
          </t-chat-item>
        </template>
        <template #footer>
          <div class="chat-input">
            <t-chat-input :value="inputValue" @update:value="val => inputValue = val" :stop-disabled="isStreamLoad"
              @send="inputEnter" @stop="onStop"> </t-chat-input>
          </div>
        </template>
      </t-chat>
      <t-dialog v-model:visible="showFeedbackForm" title="反馈信息" :mask-closable="false" :show-close="false"
        width="500px">
        <div>
          <p>反馈：</p>
          <t-textarea v-model="feedbackContent" placeholder="请输入您的反馈（可选）" :rows="4" />
        </div>
        <template #footer>
          <t-button @click="closeFeedbackForm">取消</t-button>
          <t-button type="primary" @click="submitFeedback">提交</t-button>
        </template>
      </t-dialog>
      <t-dialog v-model:visible="showDownloadModal" title="下载对话" :mask-closable="false" :show-close="true"
        width="400px">
        <div>
          <p>请选择下载格式：</p>
          <t-radio-group v-model="downloadFormat">
            <t-radio :value="'markdown'">Markdown</t-radio>
            <t-radio :value="'word'">Word</t-radio>
            <t-radio value="html">HTML</t-radio>
          </t-radio-group>
        </div>
        <template #footer>
          <t-button @click="closeDownloadModal">取消</t-button>
          <t-button type="primary" @click="downloadSelectedFormat">下载</t-button>
        </template>
      </t-dialog>
      <t-dialog v-model:visible="showShareModal" title="分享对话" :mask-closable="true" :show-close="true" width="400px">
        <div>
          <p>请选择分享平台：</p>
          <t-radio-group v-model="sharePlatform">
            <t-radio :value="'qq-zero'">QQ空间</t-radio>
            <t-radio :value="'circle'">朋友圈</t-radio>
            <t-radio :value="'wechat'">微信好友</t-radio>
          </t-radio-group>
        </div>
        <template #footer>
          <t-button @click="closeShareModal">取消</t-button>
          <t-button type="primary" @click="handleShareSuccess">分享</t-button>
        </template>
      </t-dialog>
      <t-dialog v-model:visible="showDeleteConfirm" title="确认删除" :mask-closable="false" :show-close="false"
        width="400px">
        <div>
          <p>您确定要删除这条消息吗？</p>
        </div>
        <template #footer>
          <t-button @click="cancelDelete">取消</t-button>
          <t-button type="primary" @click="confirmDeleteMessage">确认删除</t-button>
        </template>
      </t-dialog>
    </div>
  </div>
</template>

<script setup>
import { ref, nextTick } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { saveAs } from 'file-saver';
import { DeleteIcon, ChatIcon, ViewListIcon, DownloadIcon, ShareIcon, Share1Icon, SearchIcon } from 'tdesign-icons-vue-next';
import html2pdf from 'html2pdf.js';
import html2canvas from 'html2canvas';

const showDeleteConfirm = ref(false);
const deleteIndex = ref(null);

const searchQuery = ref('');
const searchResults = ref([]);

const handleSearch = () => {
  if (!searchQuery.value.trim()) {
    searchResults.value = [];
    return;
  }
  const keyword = searchQuery.value.trim();
  searchResults.value = [];
  chatList.value.forEach((msg) => {
    msg.highlightedContent = null;
    const contentLower = msg.content.toLowerCase();
    const keywordLower = keyword.toLowerCase();
    const index = contentLower.indexOf(keywordLower);
    if (index !== -1) {
      const highlighted = highlightKeyword(msg.content, keyword);
      msg.highlightedContent = highlighted;
      const snippet = createSnippet(msg.content, index, keyword.length);
      searchResults.value.push({
        messageId: msg.id,
        name: msg.name,
        datetime: msg.datetime,
        snippet: highlightKeyword(snippet, keyword),
      });
    }
  });
};

function highlightKeyword(text, keyword) {
  const regex = new RegExp(keyword, 'gi');
  return text.replace(regex, (match) => `<mark>${match}</mark>`);
}

function createSnippet(text, index, keywordLen) {
  const snippetLength = 30;
  const start = Math.max(0, index - snippetLength);
  const end = Math.min(text.length, index + keywordLen + snippetLength);
  let snippet = text.substring(start, end);
  if (start > 0) {
    snippet = '...' + snippet;
  }
  if (end < text.length) {
    snippet += '...';
  }
  return snippet;
}

const jumpToMessage = async (messageId) => {
  await nextTick();
  const el = document.getElementById(`msg-${messageId}`);
  if (el) {
    el.scrollIntoView({ behavior: 'smooth', block: 'center' });
  }
};

const downloadAsWord = () => {
  const conversationHtml = chatList.value
    .map((msg) => {
      const time = new Date(msg.datetime).toLocaleString();
      const content = msg.content.replace(/<[^>]+>/g, '');
      return `<p><strong>[${time}] ${msg.name} (${msg.role}):</strong> ${content}</p>`;
    })
    .reverse()
    .join('');
  const htmlContent = `
    <html>
      <head>
        <meta charset="utf-8" />
        <title>对话记录</title>
      </head>
      <body>
        ${conversationHtml}
      </body>
    </html>
  `;
  const blob = new Blob([htmlContent], {
    type: 'application/msword;charset=utf-8'
  });
  saveAs(blob, 'conversation.doc');
};


const removeMarkdownSymbols = (text) => {
  return text
    .replace(/(\*\*|__)(.*?)\1/g, '$2')
    .replace(/(\*|_)(.*?)\1/g, '$2')
    .replace(/`{1,3}(.*?)`{1,3}/g, '$1')
    .replace(/!\[.*?\]\(.*?\)/g, '')
    .replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')
    .replace(/#+\s*(.*)/g, '$1')
    .replace(/>\s*(.*)/g, '$1')
    .replace(/-\s*(.*)/g, '$1')
    .replace(/\d+\.\s*(.*)/g, '$1')
    .replace(/\n{2,}/g, '\n');
};

const showFeedbackForm = ref(false);
const feedbackType = ref(null);
const currentFeedbackIndex = ref(null);
const feedbackContent = ref('');
const showShareModal = ref(false);
const sharePlatform = ref('wechat');
const showDownloadModal = ref(false);
const downloadFormat = ref('markdown');
const chatRef = ref(null);
const isAlternateTheme = ref(false);

const toggleTheme = () => {
  isAlternateTheme.value = !isAlternateTheme.value;
};

const openDownloadModal = () => {
  showDownloadModal.value = true;
};

const closeDownloadModal = () => {
  showDownloadModal.value = false;
};

const openShareModal = () => {
  showShareModal.value = true;
};

const closeShareModal = () => {
  showShareModal.value = false;
};

const handleShareSuccess = () => {
  closeShareModal();
  MessagePlugin.success('分享成功！');
};

const downloadSelectedFormat = () => {
  switch (downloadFormat.value) {
    case 'markdown': downloadAsMarkdown(); break;
    case 'pdf': downloadAsPDF(); break;
    case 'word': downloadAsWord(); break;
    case 'csv': downloadAsCSV(); break;
    case 'html': downloadAsHTML(); break;
    case 'image': downloadAsImage(); break;
  }
  closeDownloadModal();
};


const downloadAsMarkdown = () => {
  const markdownContent = chatList.value
    .map(msg => {
      const time = new Date(msg.datetime).toLocaleString();
      const content = msg.content.replace(/<[^>]+>/g, '');
      return `**[${time}] ${msg.name} (${msg.role})**\n\n${content}\n`;
    })
    .reverse()
    .join('\n\n');

  const blob = new Blob([markdownContent], { type: 'text/markdown;charset=utf-8' });
  saveAs(blob, 'conversation.md');
};

const downloadAsPDF = () => {
  const element = document.querySelector('.t-chat');

  const opt = {
    margin: 1,
    filename: 'conversation.pdf',
    image: { type: 'jpeg', quality: 0.98 },
    html2canvas: { scale: 2 },
    jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' }
  };
  html2pdf().from(element).set(opt).save();
};

const backBottom = () => {
  chatRef.value.scrollToBottom({
    behavior: 'smooth',
  });
};

const chatList = ref([
  {
    avatar: 'https://i.pravatar.cc/150?img=24',
    name: '百科达人',
    datetime: new Date().toLocaleString(),
    content: '你好！我是百科达人，很高兴为你提供帮助，你可以问我各种问题。',
    role: 'assistant',
  },
]);

const isGood = ref([]);
const isBad = ref([]);

const initializeFeedbackStates = () => {
  isGood.value = chatList.value.map(() => false);
  isBad.value = chatList.value.map(() => false);
};

initializeFeedbackStates();

const fetchCancel = ref(null);
const loading = ref(false);
const isStreamLoad = ref(false);

const clearConfirm = function () {
  chatList.value = [];
  initializeFeedbackStates();
};

const onStop = function () {
  if (fetchCancel.value) {
    fetchCancel.value.abort();
    loading.value = false;
    isStreamLoad.value = false;
  }
};

/**
 * Handles different types of operations based on the provided type and options.
 * 
 * @param {string} type - The type of operation to perform ('good', 'bad', or 'replay').
 * @param {object} options - An object containing additional options for the operation.
 * @param {number} options.index - The index of the item in the chat list to perform the operation on.
 * 
 * @description
 * - For 'good' type, it toggles the 'isGood' state for the specified index,
 *   sets 'isBad' to false for that index, updates feedbackType to 'good',
 *   sets currentFeedbackIndex to the specified index, and shows the feedback form.
 * - For 'bad' type, it toggles the 'isBad' state for the specified index,
 *   sets 'isGood' to false for that index, updates feedbackType to 'bad',
 *   sets currentFeedbackIndex to the specified index, and shows the feedback form.
 * - For 'replay' type, it retrieves the user query from the next item in the chat list
 *   and calls the inputEnter function with that query.
 */
const handleOperation = function (type, options) {
  const { index } = options;
  if (type === 'good') {
    isGood.value[index] = !isGood.value[index];
    if (isGood.value[index]) {
      isBad.value[index] = false;
      feedbackType.value = 'good';
      currentFeedbackIndex.value = index;
      showFeedbackForm.value = true;
    }
  } else if (type === 'bad') {
    isBad.value[index] = !isBad.value[index];
    if (isBad.value[index]) {
      isGood.value[index] = false;
      feedbackType.value = 'bad';
      currentFeedbackIndex.value = index;
      showFeedbackForm.value = true;
    }
  } else if (type === 'replay') {
    const userQuery = chatList.value[index + 1].content;
    inputEnter(userQuery);
  }
};

/**
 * Submits the user's feedback.
 * 
 * This function checks if there is a current feedback index. If none exists,
 * it displays an error message. Otherwise, it confirms the submission and
 * hides the feedback form.
 */
const submitFeedback = () => {
  if (currentFeedbackIndex.value === null) {
    MessagePlugin.error('未找到对应的消息！');
    return;
  }
  MessagePlugin.info('提交成功，我们将根据你的反馈对后面的内容进行调整！');
  showFeedbackForm.value = false;
};

/**
 * Closes the feedback form by setting the `showFeedbackForm` reactive reference to `false`.
 * This function is intended to be used as an event handler or callback to hide the feedback form when needed.
 */
const closeFeedbackForm = () => {
  showFeedbackForm.value = false;
};

/**
 * Handles the asynchronous processing of user input data.
 * 
 * This function sets loading indicators, prepares initial messages,
 * sends them using Server-Sent Events (SSE), and updates the chat list
 * based on the response. It also handles success, completion, and cancellation
 * scenarios appropriately.
 * 
 * @param {string} inputValue - The user's input message to be processed.
 */
const handleData = async (inputValue) => {
  loading.value = true;
  isStreamLoad.value = true;
  const lastItem = chatList.value[0];
  const messages = [
    { role: 'system', content: '你是一个知识渊博的百科达人，你很乐意向大家分享一些你所知道的知识。', },
    { role: 'user', content: inputValue, }
  ];
  fetchSSE(messages, {
    success(result) {
      loading.value = false;
      const { data } = result;
      const cleanedContent = removeMarkdownSymbols(data?.delta?.content || '');
      lastItem.content += cleanedContent;
    },
    complete(isOk, msg) {
      if (!isOk || !lastItem.content) {
        lastItem.role = 'error';
        lastItem.content = msg;
      }
      isStreamLoad.value = false;
      loading.value = false;
    },
    cancel(cancel) {
      fetchCancel.value = cancel;
    },
  });
};

const inputEnter = function (inputValue) {
  if (isStreamLoad.value) {
    return;
  }
  if (!inputValue) return;

  const modifiedInput = `${inputValue}`;

  const params = {
    avatar: 'https://i.pravatar.cc/150?img=2',
    name: '自己',
    datetime: new Date().toLocaleString(),
    content: `${inputValue}`,
    role: 'user',
  };
  chatList.value.unshift(params);

  const params2 = {
    avatar: 'https://i.pravatar.cc/150?img=24',
    name: '百科达人',
    datetime: new Date().toLocaleString(),
    content: '',
    role: 'assistant',
  };
  chatList.value.unshift(params2);
  isGood.value.unshift(false);
  isBad.value.unshift(false);
  handleData(modifiedInput);
};

const fetchSSE = async (messages, options) => {
  const { success, fail, complete, cancel } = options;
  const controller = new AbortController();
  const { signal } = controller;
  cancel?.(controller);
  const apiKey = 'sk-6R0hq8U7v3bSbT1u41Lp6kPRwAgf9wnW73WgvSC7WUI73eRO';
  const responsePromise = fetch('/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer${apiKey ? ` ${apiKey}` : ''}`,
    },
    body: JSON.stringify({
      messages,
      model: 'hunyuan-pro',
      stream: true,
    }),
    signal,
  }).catch((e) => {
    const msg = e.toString() || '流式接口异常';
    complete?.(false, msg);
    return Promise.reject(e);
  });

  responsePromise
    .then((response) => {
      if (!response?.ok) {
        complete?.(false, response.statusText);
        fail?.();
        throw new Error('Request failed');
      }
      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      if (!reader) throw new Error('No reader available');

      const bufferArr = [];
      let dataText = '';
      const event = { type: null, data: null };

      async function processText({ done, value }) {
        if (done) {
          complete?.(true);
          return Promise.resolve();
        }
        const chunk = decoder.decode(value);
        const buffers = chunk.toString().split(/\r?\n/);
        bufferArr.push(...buffers);
        let i = 0;
        while (i < bufferArr.length) {
          const line = bufferArr[i];
          if (line) {
            dataText += line;
            const response = line.slice(6);
            if (response === '[DONE]') {
              event.type = 'finish';
              dataText = '';
            } else {
              const choices = JSON.parse(response.trim())?.choices?.[0];
              if (choices.finish_reason === 'stop') {
                event.type = 'finish';
                dataText = '';
              } else {
                event.type = 'delta';
                event.data = choices;
              }
            }
          }
          if (event.type && event.data) {
            const jsonData = JSON.parse(JSON.stringify(event));
            success(jsonData);
            event.type = null;
            event.data = null;
          }
          bufferArr.splice(i, 1);
        }
        return reader.read().then(processText);
      }

      return reader.read().then(processText);
    })
    .catch(() => {
      fail?.();
    });
};

const downloadConversation = () => {
  const conversation = chatList.value
    .map((msg) => {
      const time = new Date(msg.datetime).toLocaleString();
      const content = msg.content.replace(/<[^>]+>/g, '');
      return `[${time}] ${msg.name} (${msg.role}): ${content}`;
    })
    .reverse()
    .join('\n\n');

  const blob = new Blob([conversation], { type: 'text/plain;charset=utf-8' });
  saveAs(blob, 'conversation.txt');
};

const shareConversation = async () => {
  const conversation = chatList.value
    .map((msg) => {
      const time = new Date(msg.datetime).toLocaleString();
      const content = msg.content.replace(/<[^>]+>/g, '');
      return `[${time}] ${msg.name} (${msg.role}): ${content}`;
    })
    .reverse()
    .join('\n\n');

  if (navigator.share) {
    try {
      await navigator.share({
        title: '对话记录',
        text: conversation,
      });
      MessagePlugin.success('分享成功！');
    } catch (error) {
      MessagePlugin.error('分享失败或被取消');
      console.error('分享错误:', error);
    }
  } else if (navigator.clipboard) {
    try {
      await navigator.clipboard.writeText(conversation);
      MessagePlugin.success('对话已复制到剪贴板');
    } catch (error) {
      MessagePlugin.error('复制到剪贴板失败');
      console.error('剪贴板错误:', error);
    }
  } else {
    MessagePlugin.error('您的浏览器不支持分享功能');
  }
};

const confirmDelete = (index) => {
  deleteIndex.value = index;
  showDeleteConfirm.value = true;
};

const confirmDeleteMessage = () => {
  if (deleteIndex.value !== null) {
    chatList.value.splice(deleteIndex.value, 1);
    isGood.value.splice(deleteIndex.value, 1);
    isBad.value.splice(deleteIndex.value, 1);
    MessagePlugin.success('消息已删除');
    deleteIndex.value = null;
    showDeleteConfirm.value = false;
  }
};

const cancelDelete = () => {
  deleteIndex.value = null;
  showDeleteConfirm.value = false;
};

const inputValue = ref('');

const quoteMessage = (index) => {
  const quotedContent = chatList.value[index].content;
  inputValue.value = `> ${quotedContent}`;
};
const downloadAsCSV = () => {
  let csvContent = '时间,发送者,角色,内容\n';
  chatList.value
    .slice()
    .reverse()
    .forEach(msg => {
      const time = new Date(msg.datetime).toLocaleString();
      const content = msg.content.replace(/<[^>]+>/g, '');
      const row = `"${time}","${msg.name}","${msg.role}","${content.replace(/"/g, '""')}"`;
      csvContent += row + '\n';
    });
  const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
  saveAs(blob, 'conversation.csv');
};

const downloadAsHTML = () => {
  const conversationHtml = chatList.value
    .map((msg) => {
      const time = new Date(msg.datetime).toLocaleString();
      const content = msg.content;
      return `<div style="margin-bottom: 1em;">
        <strong>[${time}] ${msg.name} (${msg.role})</strong><br>
        <div>${content}</div>
      </div>`;
    })
    .reverse()
    .join('');
  const htmlContent = `
    <html>
      <head>
        <meta charset="utf-8" />
        <title>对话记录</title>
      </head>
      <body>
        ${conversationHtml}
      </body>
    </html>
  `;
  const blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' });
  saveAs(blob, 'conversation.html');
};

const downloadAsImage = async () => {
  const element = document.querySelector('.t-chat');
  if (!element) {
    console.error('未找到.t-chat');
    return;
  }
  try {
    const canvas = await html2canvas(element, {
      scale: 2,
    });
    const dataURL = canvas.toDataURL('image/png');
    const link = document.createElement('a');
    link.href = dataURL;
    link.download = 'conversation.png';
    link.click();
  } catch (error) {
    console.error('长图截取失败:', error);
  }
};
</script>

<script>
import { ref } from 'vue';
import { renderToString } from 'katex';

const parseLatex = (text) => {
  if (!text) return '';
  let blockRegex = /\$\$([^$]+)\$\$/g;
  text = text.replace(blockRegex, (match, group1) => {
    try {
      return renderToString(group1, { displayMode: true });
    } catch (error) {
      console.error('KaTeX block parse error:', error);
      return match;
    }
  });
  let inlineRegex = /\$([^$]+)\$/g;
  text = text.replace(inlineRegex, (match, group1) => {
    try {
      return renderToString(group1, { displayMode: false });
    } catch (error) {
      console.error('KaTeX inline parse error:', error);
      return match;
    }
  });
  return text;
};
</script>
